package org.mika.asm;

import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.util.HashMap;

import net.sf.cglib.asm.ClassWriter;
import net.sf.cglib.asm.Type;
import net.sf.cglib.core.ClassEmitter;
import net.sf.cglib.core.ClassGenerator;
import net.sf.cglib.core.CodeEmitter;
import net.sf.cglib.core.EmitUtils;
import net.sf.cglib.core.GeneratorStrategy;
import net.sf.cglib.core.ReflectUtils;

class FieldsOpenerGenerator  implements GeneratorStrategy {
	static final String READ  = "read$$";
	static final String WRITE = "write$$";
	private static final String FIELDS = "$$fields";
	
	private Class<?> clazz, iface;
	
	public FieldsOpenerGenerator(Class<?> clazz, Class<?> iface) {
		this.clazz = clazz;
		this.iface = iface;
	}
	
	public static Object read$$(Object o, String property) {
		throw new NotImplementedException("Method read$$ is inaccessible within this context");
	}
	
	public static void write$$(Object o, String property, Object value) {
		throw new NotImplementedException("Method write$$ is inaccessible within this context");
	}
	
	@Override
	public byte[] generate(ClassGenerator cg) throws Exception {
		ClassWriter  writer  = new ClassWriter(true);
		ClassEmitter ce = new ClassEmitter(writer);
		
		ASMUtils.generateOpenerHeader (ce, clazz, iface);
		ASMUtils.generateConstructors (ce, clazz);
		ASMUtils.declareHashMap       (ce, FIELDS);
		
		{
			CodeEmitter se = ce.begin_static();
			ASMUtils.initHashMap(ce, se, FIELDS);				
			
			for(PropertyDescriptor pd : Introspector.getBeanInfo(iface).getPropertyDescriptors()) {
				se.getstatic(ce.getClassType(), FIELDS, Type.getType(HashMap.class));								
				se.push(pd.getName());
				
				EmitUtils.push_object(se, clazz);
				se.push(pd.getName());
				
				se.invoke_virtual(
					Type.getType(Class.class),
					ReflectUtils.getSignature(
						Class.class.getDeclaredMethod("getDeclaredField", new Class[]{String.class})
					)
				);
				
				se.dup();
				se.push(true);
				
				se.invoke(
					ReflectUtils.getMethodInfo(
						Field.class.getMethod("setAccessible", new Class[]{
							boolean.class
						})
					)
				);
				
				se.invoke(					
					ReflectUtils.getMethodInfo(
						HashMap.class.getMethod("put", new Class[]{Object.class, Object.class})
					)						
				);
			}
			
			se.end_method();
		}
		
		{
			CodeEmitter getter = EmitUtils.begin_method(
				ce, ReflectUtils.getMethodInfo(
					FieldsOpenerGenerator.class.getMethod(READ, new Class<?>[] {
						Object.class, String.class
					})
				) 
			);
			
			getter.getstatic( ce.getClassType(), FIELDS, Type.getType(HashMap.class) );
			getter.load_arg(1);
			
			getter.invoke(
				ReflectUtils.getMethodInfo(
					HashMap.class.getMethod("get", new Class[]{ Object.class })
				)
			);
			
			getter.checkcast(Type.getType( Field.class ));
			
			getter.load_arg(0);
			
			getter.invoke(
				ReflectUtils.getMethodInfo(
					Field.class.getMethod("get", new Class[]{ Object.class })
				)
			);
			
			getter.return_value();
			getter.end_method();
		}
		
		{
			CodeEmitter setter = EmitUtils.begin_method(
				ce, ReflectUtils.getMethodInfo(
					FieldsOpenerGenerator.class.getMethod(WRITE, new Class<?>[] {
						Object.class, String.class, Object.class
					})
				)
			);
			
			setter.getstatic( ce.getClassType(), FIELDS, Type.getType(HashMap.class) );
			setter.load_arg(1);
			
			setter.invoke(
				ReflectUtils.getMethodInfo(
					HashMap.class.getMethod("get", new Class[]{ Object.class })
				)
			);
			
			setter.checkcast(Type.getType( Field.class ));
			
			setter.load_arg(0);
			setter.load_arg(2);
			
			setter.invoke(
				ReflectUtils.getMethodInfo(
					Field.class.getMethod("set", new Class[]{ Object.class, Object.class })
				)
			);						
			
			setter.return_value();			
			setter.end_method();
		}
		
		ce.end_class();
		return writer.toByteArray();
	}
}
